iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 12
0
Software Development

利用Swift 4開發iOS App,Daily Work List系列 第 12

Day 12. Develop Add Event Page View Controller 2

  • 分享至 

  • xImage
  •  

接著前天的,我們已經把頁面上需要的元件關聯都設定好了,所以剩下的部分都可以單純的在AddEventViewController中完成,這邊先提一下,因為在開發中的過程,不斷有做不同的調整,所以偶爾會看到畫面或程式碼怎麼跟前幾天的不一樣了?!這是難免的啦XD,但我有盡量在有異動後去調整先前的文章,若有遺漏的,請大家多包涵~

今天這篇會講幾個東西

  • UIDatePicker 日期時間選單
  • UIPickerView 下拉選單
  • Property List 儲存方式
  • Insert Data,利用先前建立好的SQLiteManger.swift

UIDatePicker

在我們的Add New Event畫面上會用到的日期元件有三個:Start Date、End Date、Time,因此我們會需要建立以下的變數

let startDatePicker = UIDatePicker()    // Start Date的DatePicker
let endDatePicker = UIDatePicker()   // End Date的DatePicker
let timePicker = UIDatePicker()   // Time的DatePicker
let showDateFormatter = DateFormatter()    // 要顯示出來看的日期格式
let showTimeFormatter = DateFormatter()    // 要顯示出來看的時間格式
let dateFormatter = DateFormatter()    // 欲儲存進資料庫的日期格式
let timeFormatter = DateFormatter()    // 欲儲存進資料庫的時間格式

接著,在viewDidLoad()的時候加入各自的設定

  • 顯示的日期格式:YYYY-MM-DD,2018-10-11
  • 顯示的時間格式:HH:mi,14:00
  • 資料庫的日期格式:YYYYMMDD,20181011
  • 資料庫的時間格式:HHmi,1400
// 設置時間顯示的格式
showDateFormatter.dateFormat = "yyyy-MM-dd"
showTimeFormatter.dateFormat = "HH:mi"
dateFormatter.dateFormat = "yyyyMMdd"
timeFormatter.dateFormat = "HHmi"

// 設置 UIDatePicker 格式,StartDate為日期類型
startDatePicker.datePickerMode = .date
// 設置顯示的語言環境,台灣
startDatePicker.locale = Locale(identifier: "zh_TW")
// 設置改變日期時間時會執行動作的方法
startDatePicker.addTarget(self, action: #selector(changeStartDate), for: .valueChanged)
// 將DatePicker綁定到TextField上
startDate.inputView = startDatePicker
// 因為起始日為必填值,因此給他初始化今天日期
changeStartDate()

// 設置 UIDatePicker 格式,StartDate為日期類型
endDatePicker.datePickerMode = .date
// 設置顯示的語言環境,台灣
endDatePicker.locale = Locale(identifier: "zh_TW")
// 設置改變日期時間時會執行動作的方法
endDatePicker.addTarget(self, action: #selector(changeEndDate), for: .valueChanged)
// 將DatePicker綁定到TextField上
endDate.inputView = endDatePicker

// 設置 UIDatePicker 格式,StartDate為時間類型
timePicker.datePickerMode = .time
// 設置顯示的語言環境,台灣
timePicker.locale = Locale(identifier: "zh_TW")
// 設置改變日期時間時會執行動作的方法
timePicker.addTarget(self, action: #selector(changeExecTime), for: .valueChanged)
// 將DatePicker綁定到TextField上
execTime.inputView = timePicker

然後,我們在每個DatePicker上都加入了當值改變(.valueChanged)就要呼叫各自的方法(#selector(xxxxx)),方法內容其實也只是要把DatePicker的內容顯示在TextField上,不然選完就看不到選了什麼

@objc func changeStartDate() {
    startDate.text = showDateFormatter.string(from: startDatePicker.date)
}

@objc func changeEndDate() {
    endDate.text = showDateFormatter.string(from: endDatePicker.date)
}

@objc func changeExecTime() {
    execTime.text = showTimeFormatter.string(from: timePicker.date)
}

這樣....就完成了喔!!!有沒有很快!
結果的畫面如下

https://ithelp.ithome.com.tw/upload/images/20181012/20111916h1lsdwknq0.png https://ithelp.ithome.com.tw/upload/images/20181012/20111916KKiFBrONL7.png
https://ithelp.ithome.com.tw/upload/images/20181012/20111916s0U5h1yXAT.png

UIPickerView

這個元件,我們用在Category上,且當初我設計的時候有故意保留未來可以給使用這自己設定的空間,因此我會搭配著Property List一起使用,先來看Picker吧

一樣先命名變數

// 顯示選單的Picker
let pickerView = UIPickerView()
// Picker內的參數,從plist中取得
var pickerData: [(code: Int, name: String)]! = []
// 被選取的選項,對應要寫入資料庫的值
var categoryCode: Int = 0

接著於viewDidLoad()中加入設定

// 設置顯示選取的資料,例如:選取了Job,再次開啟Picker時,就會顯示在Job上
pickerView.showsSelectionIndicator = true
pickerView.delegate = self
pickerView.dataSource = self
// 將PickerView加入對應的TextField中
categoryPicker.inputView = pickerView

這邊要注意一下,因為delegate和dataSource都指向self,也就是viewController本身,因此要記得加入協定

class AddEventViewController: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate {

然後設定PickerView的相關功能以及協定需寫入的funciton

// Sets number of columns in picker view
func numberOfComponents(in pickerView: UIPickerView) -> Int {
    return 1
}

// Sets the number of rows in the picker view
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
    return pickerData.count
}

// This function sets the text of the picker view to the content of the "salutations" array
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
    return pickerData[row].name
}

// When user selects an option, this function will set the text of the text field to reflect the selected option.
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
    // 將被選中的資料name顯示至畫面、code存於變數,之後寫入資料庫要使用
    categoryPicker.text = pickerData[row].name
    categoryCode = pickerData[row].code
}

那基本上,PickerView的部份這樣就完成了喔!只是因為現在沒有資料在pickerData裡面,所以都會是空的

Property List

先新建一個Property List 檔案,命名為Category.plist
https://ithelp.ithome.com.tw/upload/images/20181012/20111916fubxvmjF37.png

接著左側選取該檔案,後於右側可圖像編輯加入資料,這裡使用Array包Dictionary的方式,然後Dictionary裡面分別有code與name兩個key-value
https://ithelp.ithome.com.tw/upload/images/20181012/201119166CHvfTSGx8.png

建立完成後,回到AddEventViewController.viewDidLoad()裡面,要把資料撈出來

// 撈取設定檔的pickerData
if let path = Bundle.main.path(forResource: "Category", ofType: "plist"),
    let array = NSArray(contentsOfFile: path) {
    // Use your myDict here
    for case let category as NSDictionary in array {
        pickerData.append((code:category.object(forKey: "code") as! Int, name: category.object(forKey: "name") as! String))
    }
}

https://ithelp.ithome.com.tw/upload/images/20181012/20111916J1HTKwJQjf.png

Insert Data

終於到新增資料進資料庫了!當點選右上角的「Save」按鈕,就會儲存資料,程式碼寫在前天建立出來的function save()裡,在寫入DB之前有幾項資料檢核需要先做

  • 若nameText的值為空,需顯示錯誤訊息
  • 若StartDate的值為空,需顯示錯誤訊息
  • 如果Repeat 非一次性,需要有End Date
  • 若有End Date,結束日不可大於等於起始日
if nameText.text! == "" {
    confirmAlert("Name is required!")
} else if startDate.text == "" {
    confirmAlert("Start Date is required!")
} else if repeatButton.selectedSegmentIndex != 0 && endDate.text == "" {
    confirmAlert("If event is routine, end date is required.")
} else if endDate.text != "" && endDatePicker.date <= startDatePicker.date {
    confirmAlert("End date cannot same with or earlier than start date.")
} else {
    // valid
}

如果資料都正確,就可以準備寫入,在AddEventViewController要使用SQLiteManager,是可以用Create一個新的方式,但每次建立一個新的,就會執行一次init(),建立一次連線,這樣多次下來,難保不會有狀況,因此我把它置於APP一起動時就建立(AppDelegate.swift),讓不同的ViewController也都可以調用同一個
https://ithelp.ithome.com.tw/upload/images/20181012/20111916qjQs5zT9RT.png

再回到AddEventViewController中加入,就可以使用了喔

let sqlManager = (UIApplication.shared.delegate as! AppDelegate).sqlManager

接著,因為我們有四種repeat方式,我們就刻意來使用昨天有提到的Enum類型吧

enum RepeatType: Int {
    case Once = 0
    case Daily = 1
    case Weekly = 2
    case Monthly = 3
}

這不同的Repeat方式需要計算日期,因此使用Calendar來協助日期與月份的加減,就可以避開幾月大幾月小和可怕的閏年非閏年的問題,很方便,然後搭配do-while迴圈寫入,這個邏輯不難,身為工程師的大家應該可以輕鬆理解

let calendar = Calendar.current
var execDate = startDatePicker.date
// Repeat
repeat {
    // insert table
    switch repeatButton.selectedSegmentIndex {
    case RepeatType.Once.rawValue:
        // Repeat == Once
        break
    case RepeatType.Daily.rawValue:
        // Repeat == Daily
        execDate = calendar.date(byAdding: .day, value: 1, to: execDate)!
    case RepeatType.Weekly.rawValue:
        // Repeat == Weekly
        execDate = calendar.date(byAdding: .day, value: 7, to: execDate)!
    case RepeatType.Monthly.rawValue:
        // Repeat == Monthly
        execDate = calendar.date(byAdding: .month, value: 1, to: execDate)!
    default:
        print("error repeat type")
    }
} while endDate.text != "" && execDate <= endDatePicker.date

然後,寫入資料庫就呼叫SQLiteManager裡面的方法吧

sqlManager.insertEvent(day: dateFormatter.string(from: execDate), name: nameText.text!, time: execTimeText, category: categoryCode, reminder: reminderSwitch.isOn, place: placeText.text, lat: lat, lng: lng, note: noteText.text)

最後,新增完成要關掉Add New Event的View

dismiss(animated: true, completion: nil)

如此就完成了喔!

附上完整的save()程式碼:

@IBAction func save(_ sender: UIBarButtonItem) {
    if nameText.text! == "" {
        confirmAlert("Name is required!")
    } else if startDate.text == "" {
        confirmAlert("Start Date is required!")
    } else if repeatButton.selectedSegmentIndex != 0 && endDate.text == "" {
        confirmAlert("If event is routine, end date is required.")
    } else if endDate.text != "" && endDatePicker.date <= startDatePicker.date {
        confirmAlert("End date cannot same with or earlier than start date.")
    } else {
        // 若有設定時間,才寫入HHMi格式,否則為nil
        var execTimeText: String? = nil
        if execTime.text != "" {
            execTimeText = timeFormatter.string(from: timePicker.date)
        }
        
        let calendar = Calendar.current
        var execDate = startDatePicker.date
        // Repeat
        repeat {
            sqlManager.insertEvent(day: dateFormatter.string(from: execDate), name: nameText.text!, time: execTimeText, category: categoryCode, reminder: reminderSwitch.isOn, place: placeText.text, lat: lat, lng: lng, note: noteText.text)
            switch repeatButton.selectedSegmentIndex {
            case RepeatType.Once.rawValue:
                // Repeat == Once
                break
            case RepeatType.Daily.rawValue:
                // Repeat == Daily
                execDate = calendar.date(byAdding: .day, value: 1, to: execDate)!
            case RepeatType.Weekly.rawValue:
                // Repeat == Weekly
                execDate = calendar.date(byAdding: .day, value: 7, to: execDate)!
            case RepeatType.Monthly.rawValue:
                // Repeat == Monthly
                execDate = calendar.date(byAdding: .month, value: 1, to: execDate)!
            default:
                print("error repeat type")
            }
        } while endDate.text != "" && execDate <= endDatePicker.date
        
        dismiss(animated: true, completion: nil)
    }
}

上一篇
Day 11. Talking About Enum, Struct and Class
下一篇
Day 13. Develop Day Page Storyboard
系列文
利用Swift 4開發iOS App,Daily Work List31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言